package com.xyzwps.server.paimon.model.service

import com.xyzwps.server.paimon.error.Errors
import com.xyzwps.server.paimon.infra.service.VcodeGenerator
import com.xyzwps.server.paimon.model.entity.PhoneVcode
import com.xyzwps.server.paimon.value.VcodeScene
import com.xyzwps.server.paimon.model.repository.PhoneVcodeRepository
import org.springframework.stereotype.Service
import com.xyzwps.server.paimon.common.Utils.genUid
import com.xyzwps.server.paimon.ext.instantNow
import com.xyzwps.server.paimon.ext.minusDays
import com.xyzwps.server.paimon.ext.minusMinutes
import com.xyzwps.server.paimon.value.Phone
import org.slf4j.LoggerFactory
import kotlin.time.Instant

@Service
class PhoneVcodeService(
    private val phoneVcodeRepository: PhoneVcodeRepository,
    private val vcodeGenerator: VcodeGenerator
) {
    companion object {
        private val log = LoggerFactory.getLogger(PhoneVcodeService::class.java)
    }

    private fun oneMinuteAgo(): Instant {
        return instantNow().minusMinutes(1)
    }

    private fun oneDayAgo(): Instant {
        return instantNow().minusDays(1)
    }

    fun validate(phone: Phone, vcode: String, dkey: String, scene: VcodeScene) {
        // TODO: 避免重试太多次
        val vcodes = phoneVcodeRepository.findByPhoneAndSceneAndCreatedAtAfter(phone, scene, oneMinuteAgo())
            .filter { !it.used }

        if (vcodes.isEmpty()) {
            log.error("vcode not found for phone={}, scene={}", phone, scene)
            throw Errors.VCODE_INVALID.toEx()
        }

        if (vcodes.size > 1) {
            log.warn("IMPOSSIBLE: there are more than one vcodes for phone={}, scene={}", phone, scene)
        }

        val v = vcodes.first()
        if (v.vcode != vcode) {
            log.error("vcode not match for phone={}, scene={}, vcode={}", phone, scene, vcode)
            throw Errors.VCODE_INVALID.toEx()
        }
        if (v.dkey != dkey) {
            log.error("dkey not match for phone={}, scene={}, dkey={}", phone, scene, dkey)
            throw Errors.VCODE_INVALID.toEx()
        }

        val newV = v.copy(used = true, usedAt = instantNow())
        phoneVcodeRepository.save(newV)
    }

    fun generate(phone: Phone, scene: VcodeScene): PhoneVcode {
        val vcodeInOneDay = phoneVcodeRepository.findByPhoneAndSceneAndCreatedAtAfter(phone, scene, oneDayAgo())
        if (vcodeInOneDay.size >= 5) {
            throw Errors.VCODE_TOO_FREQUENT_24H.toEx()
        }

        val vcodes = phoneVcodeRepository.findByPhoneAndSceneAndCreatedAtAfter(phone, scene, oneMinuteAgo())
        if (vcodes.isEmpty()) {
            return generateAndSave(phone, scene)
        }

        throw Errors.VCODE_TOO_FREQUENT_60S.toEx()
    }

    fun dkey(): String {
        return genUid()
    }

    private fun generateAndSave(phone: Phone, scene: VcodeScene): PhoneVcode {
        val vcode = PhoneVcode(
            id = null,
            phone = phone,
            scene = scene,
            vcode = vcodeGenerator.generate(),
            dkey = dkey(),
            createdAt = instantNow(),
            used = false,
            usedAt = null
        )
        phoneVcodeRepository.save(vcode)
        return vcode
    }
}
